﻿using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Threading.Tasks;
using System.Xml.Linq;

namespace DX.IO
{
    class ReadonlyStream : Stream
    {

        public ReadonlyStream(byte[] bytes)
        {
            this.bytes = bytes;
        }

        private readonly byte[] bytes;
        public override bool CanRead => true;

        public override bool CanSeek => true;

        public override bool CanWrite => false;

        public override long Length => bytes.LongLength;

        public override long Position { get; set; }

        public override void Flush()
        {

        }

        public override int Read(byte[] buffer, int offset, int count)
        {
            Array.Copy(bytes, offset, buffer, 0, count);
            return count;
        }

        public override long Seek(long offset, SeekOrigin origin)
        {
            var orgPosition = Position;
            if (origin == SeekOrigin.Begin)
            {

                Position = 0 + offset;
                return orgPosition - offset;
            }
            return -1;
        }

        public override void SetLength(long value)
        {
            throw new NotImplementedException();
        }

        public override void Write(byte[] buffer, int offset, int count)
        {
            throw new NotImplementedException();
        }

    }
    public abstract class FileEntry : System.IDisposable
    {
        private bool disposedValue;

        protected FileEntry(string fullName)
        {
            FullName = fullName;
            Name = Path.GetFileName(FullName);
        }
        ~FileEntry()
        {
            Dispose(true);
        }
        public virtual string Name { get; protected set; }
        public virtual string FullName { get; protected set; }


        public abstract byte[] GetBuffer();
        public abstract Stream OpenRead();

        public abstract Stream OpenWrite();

        #region Disposable

        protected abstract void OnDispose();

        protected virtual void Dispose(bool disposing)
        {
            if (!disposedValue)
            {
                if (disposing)
                {
                    OnDispose();
                }

                
                disposedValue = true;
            }
        }



        public void Dispose()
        { 
            Dispose(disposing: true);
            GC.SuppressFinalize(this);
        }
        #endregion
        #region Files

        public static FileEntry Buffer(string name, byte[] buffer)
        {
            return new BufferFileEntry(name, buffer);
        }

        public static FileEntry Physics(string fileName)
        {
            return new PhysicsFileEntry(fileName);
        }

        public static FileEntry Stream(string name, Stream stream, long? length)
        {
            return new StreamFileEntry(name, stream, length);
        }

        #endregion
    }

    class StreamFileEntry : FileEntry
    {

        public StreamFileEntry(string name, Stream stream, long? length) : base(name)
        {

            this.stream = stream;
            this.length = length;
        }
        private Stream stream;
        private readonly long? length;
        byte[] allbuffer;
        public override byte[] GetBuffer()
        {
            if (stream is MemoryStream memory) return memory.ToArray();
            if (allbuffer != null) return allbuffer;
            if (stream.Position != 0 && !stream.CanSeek) throw new FileEntryException("cat not seek");
            stream.Seek(0, SeekOrigin.Begin);
            if (length == null)
            {
                List<byte> list = new List<byte>();
                byte[] buffer = new byte[1024 * 4];
                while (true)
                {
                    var count = stream.Read(buffer, 0, buffer.Length);
                    if (count == 0) break;
                    list.AddRange(buffer.Take(count));

                }
                allbuffer = list.ToArray();
            }
            else
            {

                allbuffer = new byte[length.Value];
                stream.ReadExactly(allbuffer);
            }
            return allbuffer;
        }

        public override Stream OpenRead()
        {
            if (stream.CanRead) return stream;
            throw new NotSupportedException();
        }
        public override Stream OpenWrite()
        {
            if (stream.CanWrite) return stream;
            throw new NotSupportedException();
        }
        protected override void OnDispose()
        {
         
            allbuffer = null;
            if (stream != null)
            {
                stream.Dispose();
                stream = null;
            }
        }

    }
    class ContentFileEntry : BufferFileEntry
    {
        public ContentFileEntry(string name, string content) : this(name, content, System.Text.Encoding.UTF8)
        {

        }
        public ContentFileEntry(string name, string content, System.Text.Encoding encoding) : base(name, encoding.GetBytes(content))
        {
            Content = content;
        }

        public string Content { get; }
    }
    class BufferFileEntry : FileEntry
    {

        public BufferFileEntry(string name, byte[] buffer) : base(name)
        {

            this.buffer = buffer;
        }
        private byte[] buffer;

        public override byte[] GetBuffer()
        {
            return buffer;
        }
        Stream stream;
        public override Stream OpenRead()
        {

            stream = new ReadonlyStream(buffer);

            return stream;
        }
        public override Stream OpenWrite()
        {
            throw new NotSupportedException();
        }
        protected override void OnDispose()
        {
           
            if (buffer != null)
            {

                buffer = null;
            }

            if (stream != null)
            {
                stream.Dispose();
            }




        }

    }
    class PhysicsFileEntry : FileEntry
    {

        public PhysicsFileEntry(string path) : base(path)
        {
            if (!File.Exists(path))
            {
                throw new FileNotFoundException();
            }
            fileInfo = new FileInfo(path);
        }
        FileInfo fileInfo;
        Stream stream;
        public override string Name { get => fileInfo.Name; }
        public override string FullName { get => fileInfo.FullName; }
        public override byte[] GetBuffer()
        {
            return File.ReadAllBytes(fileInfo.FullName);
        }

        public override Stream OpenRead()
        {
            if (stream != null) throw new FileEntryException("File is Open");
            stream = fileInfo.OpenRead();

            return stream;

        }
        public override Stream OpenWrite()
        {
            if (stream != null) throw new FileEntryException("File is Open");
            stream = fileInfo.OpenWrite();

            return stream;

        }
        protected override void OnDispose()
        {
       
            if (stream != null)
            {
                stream.Dispose();
                stream = null;
            }
        }

    }
}